<?php if ( ! defined('BASEPATH')) exit('This file does not run directly. Sorry.'); ?>
<?php
/**
 * Kalaisoo.
 *
 * A lightweight no-framework framework written in PHP
 *
 * @package Kalaisoo
 * @author $Author: info@sah-company.com $
 * @version $Id: refused.php 147 2012-01-27 10:13:23Z info@sah-company.com $
 */

/**
 * returns a string with translated text or the original text if no translation was found.
 *
 * When called the function checks if there is an translation for the given
 * token in the global array $_translation. If not the raw token is either returned or
 * echoed. If there is a translation the same happens, but the translated token
 * is used.
 *
 * Example:
 * <code>
 * <?php
 *  echo __('hello_world');
 * ?>
 * </code>
 *
 * @global $_translation
 * @param string text to translate
 * @return string translated text or original text if no translation was found
 */
function __($token)
{
	global $_translation;
	if (isset($_translation[$token])) return $_translation[$token];
	return $token;
}

/**
 * calls the stop method of Core and writes and closes the session.
 *
 * @param Core (required)
 * @param string (optional)
 */
function shutdown(Core $core, $method = 'stop')
{
	$core->$method();
	session_write_close();
}

/**
 * transforms a key/value pair array into a string.
 *
 * Example:
 * <code>
 *  <?php
 *    $text = glue(array('title' => 'Test', 'lenght' => '4'));
 *  ?>
 * </code>
 *
 * @param array (required)
 * @param string opening glue
 * @param string closing glue
 * @param string preceeding character
 * @param sting implode character
 * @return string
 */
function glue(array $dict, $glueOpen = '="', $glueClose = '"', $pre = ' ', $impChar = ' ')
{
	if (empty($dict)) return '';
	$stack = array();
	foreach ($dict as $key=>$value) {
		$stack[] = $key.$glueOpen.$value.$glueClose;
	}
	return $pre.implode($impChar, $stack);
}

/**
 * Kalaisoo Core Class.
 *
 * This class is the core of our no-framework framework.
 *
 * @package Kalaisoo
 * @subpackage System
 * @author $Author: info@sah-company.com $
 */
class Core
{
	/**
	 * name for the session cookie.
	 *
	 * @const string
	 */
	const SESSION_NAME = 'refused_sess_v1';

	/**
	 * Stores our registry object which can be accessed throughout our app.
	 *
	 * @var Registry
	 */
	protected $registry;

	/**
	 * constructs a new Core.
	 *
	 * This will construct a new core and create all needed objects in our
	 * registry. As an optional parameter a configuration array can be given.
	 *
	 * @param array $config
	 * @see app/config/config.php
	 */
	public function __construct(array $config = array())
	{
		$this->registry = Registry::instance();
		$this->registry->config = $config;
		$this->registry->loader = new Loader();
		$this->registry->logger = new Logger();
		$this->registry->stopwatch = new Stopwatch();
		$this->registry->router = new Router();
		$this->registry->cache = new Cache();
		$this->registry->request = new Request();
		$this->registry->response = new Response();
		register_shutdown_function('shutdown', $this);
		session_name(self::SESSION_NAME);
	}
	
	/**
	 * runs the Core application.
	 *
	 * This will run the core. It will check the client request and find out
	 * if there is a cached and not yet outdated cache file for the request. If so
	 * it will deliver from the cache. Otherwise it will route the request to a
	 * controller or an error page will be output.
	 *
	 * If you have defined a CMS controller in your config file, that controller is
	 * called and tries to analyze the URI again. If there is an page for that URL
	 * it will be displayed instead of an 404 error.
	 */
	public function run()
	{
		$this->registry->cache->check();
		list($language, $controller, $method, $params) = $this->registry->router->route(null, $this->registry->config['router']);
		$this->registry->language = $language;
		$this->registry->logger->log(sprintf('Core tries to run %s->%s(...)', $controller, $method));
		if ( ! file_exists(APP.'/controllers/'.$controller.EXT)) {			
			$controller = 'controller';
			$appControllerName = 'Controller';
			if (isset($this->registry->config['cms']['active']) && $this->registry->config['cms']['active']) {
				// URI will be analyzed by the CMS plugin
				$method = 'cms';
			} else {
				$method = 'error';
				$params = array('error' => __('error_404'));
			}
		}
		else {
			require APP.'/controllers/'.$controller.EXT;
			$appControllerName = $controller.'_Controller';
		}
		$appController = new $appControllerName();
		
		$this->registry->response->start();
		try {
			// this is where all the cumbersome things may happen
			call_user_func_array(array($appController, $method), $params);
		} catch (Exception $e) {
			$this->registry->response->clean();
			if (MODUS != 'DEVELOPMENT') {
				$errorText = __('system_failure_i_am_sorry');
			} else {
				$errorText = '<pre>'.htmlspecialchars($e).'</pre>';
			}
			$appController->error($errorText);
		}
		$this->registry->response->addHeader('Content-Language', $this->registry->language);
		// replacements
		$this->registry->response->addReplacement('language', $this->registry->language);
		$this->registry->response->addReplacement('elapsed_time', 
			$this->registry->stopwatch->mark('_flush')->laptime('_start', '_flush'));
		$this->registry->response->addReplacement('used_memory', 
			round(memory_get_usage() / 1048576, 2));
		// analytics replacements
		// head
		$head_analytics = '';
		if (isset($this->registry->config['analytics']['head'])) {
			$head_analytics = $this->registry->config['analytics']['head'];
		}
		$this->registry->response->addReplacement('head_analytics', $head_analytics);
		// body
		$body_analytics = '';
		if (isset($this->registry->config['analytics']['body'])) {
			$body_analytics = $this->registry->config['analytics']['body'];
		}
		$this->registry->response->addReplacement('body_analytics', $body_analytics);
		// give the response to the client
		$this->registry->response->flush();
	}
	
	/**
	 * stop the Core and log time and memory usage, then save the log files.
	 *
	 * This method is executed when our program shuts down. It calls our logger
	 * to eventually write the log file to your harddisk, depending wether you
	 * have set the switch in your configuration to do so.
	 *
	 * @uses Logger
	 */
	public function stop()
	{
		$this->registry->logger->log(sprintf('Elapsed time %ss, Memory usage %sMB', 
				$this->registry->stopwatch->mark('_stop')->laptime('_start', '_stop'), 
				round(memory_get_usage() / 1048576, 2)));
		if (isset($this->registry->config['logger']['save']) && 
				$this->registry->config['logger']['save'] === true) {
			$this->registry->logger->save();
		}
	}
}

/**
 * Kalaisoo Registry Class.
 *
 * This class allows you to access information and other classes throughout the application.
 *
 * @package Kalaisoo
 * @subpackage System
 * @author $Author: info@sah-company.com $
 */
class Registry
{
	/**
	 * the one and only instance of our Registry.
	 *
	 * @var Registry
	 */
	private static $instance = null;
	
	/**
	 * properties of the registry.
	 *
	 * @var array
	 */
	protected $values = array();
	
	/**
	 * the breadcrumbs key/value array where key is the linktext and value is the url.
	 *
	 * @var array
	 */
	protected $breadcrumbs = array();
	
	/**
	 * this array can be used to stack data
	 *
	 * @var array
	 */
	protected $stack = array();
	
	/**
	 * returns one and only one instance of Registry.
	 *
	 * @return Registry
	 */
	public static function instance()
	{
		if (self::$instance === null)
		{
			self::$instance = new Registry();
		}
		return self::$instance;
	}
	
	/**
	 * set a property to a value.
	 *
	 * Set a value in our Registry.
	 *
	 * @param string Name of the property to set
	 * @param mixed Value of the property to set
	 */
	public function __set($key, $value)
	{
		$this->values[$key] = $value;
	}
	
	/**
	 * returns the value of a property or null.
	 *
	 * Get a value from our Registry.
	 *
	 * @param string Name of the property to get
	 * @return mixed
	 */
	public function __get($key)
	{
		if ( ! isset($this->values[$key])) return null;
		return $this->values[$key];
	}
	
	/**
	 * adds a new item to the breadcrumbs.
	 *
	 * @param string $linktext
	 * @param string $url
	 * @return void
	 */
	public function addBreadcrumb($linktext, $url)
	{
		$this->breadcrumbs[] = array(
			'linktext' => $linktext,
			'url' => $url
		);
	}
	
	/**
	 * returns the breadcrumbs.
	 *
	 * @return array
	 */
	public function breadcrumbs()
	{
		return $this->breadcrumbs;
	}
	
	/**
	 * adds something to the stack.
	 *
	 * @param mixed $element
	 */
	public function addStack($element)
	{
		$this->stack[] = $element;
	}
	
	/**
	 * returns the stack.
	 *
	 * @return array
	 */
	public function stack()
	{
		return $this->stack;
	}
	
	/**
	 * constructs a new Registry.
	 *
	 * To make sure this can only be called from inside this is protected.
	 * @access protected
	 */
	protected function __construct()
	{
	}
	
	/**
	 * Clone.
	 *
	 * You can not clone this
	 */
	private function __clone()
	{
	}
}

/**
 * Kalaisoo Router Class.
 *
 * @package Kalaisoo
 * @subpackage System
 * @author $Author: info@sah-company.com $
 */
class Router
{
	/**
	 * the current url.
	 *
	 * @var string
	 */
	protected $url;
	
	/**
	 * the current internal url.
	 *
	 * @var string
	 */
	protected $internal_url;
	
	/**
	 * the name of the controller extracted from the url.
	 *
	 * @var string
	 */
	protected $controller;
	
	/**
	 * the language code extracted from the url.
	 *
	 * @var string
	 */
	protected $language;
	
	/**
	 * the sliced url.
	 *
	 * @var array
	 */
	protected $slices = array();
	
	/**
	 * constructs a new Router.
	 *
	 * This will construct our Router. Nothing special happens here. Your controllers
	 * may instanciate another router and reinterpret the URI so subsequent controllers
	 * may be execute when needed.
	 */
	public function __construct()
	{
	}
	
	/**
	 * returns an array with codes for language, controller, method and a parameter array.
	 *
	 * This will try to interpret the given URI, depending on the settings given in
	 * the optional second parameter which is an array.
	 *
	 * @param string $uri
	 * @param array $defaults
	 * @return array
	 */
	public function route($uri = null, array $defaults = array())
	{
		$this->interpret($uri);
		// the requested uri relative to our installation directory
		$this->internal_url = implode('/', array_slice($this->slices, $defaults['offset']));
		$language = $this->slice($defaults['offset']);
		if ($language === null) $language = $defaults['language'];
		$this->language = $language;
		$controller = $this->slice(1 + $defaults['offset']);
		if ($controller === null) $controller = $defaults['controller'];
		$this->controller = $controller;
		$method = $this->slice(2 + $defaults['offset']);
		if ($method === null) $method = $defaults['method'];
		$params = array_slice($this->slices, 3 + $defaults['offset']);
		return array($language, (strtolower($controller)), strtolower($method), $params);
	}
	
	/**
	 * parses the REQUEST_URI and sets url and slices and return the Router for chaning.
	 *
	 * @param mixed A URI to interpret. If not given, the REQUEST_URI is used
	 * @return Router
	 */
	public function interpret($uri = null)
	{
		if ($uri === null) $uri = $_SERVER['REQUEST_URI'];
		$parsed = parse_url($uri);
		$this->url = urldecode(trim(filter_var($parsed['path'], FILTER_SANITIZE_URL), '/'));
		$this->slices = explode('/', $this->url);
		return $this;
	}
	
	/**
	 * returns a string with the url or only language and controller.
	 *
	 * @param bool $fragment
	 * @return string
	 */
	public function url($fragment = false)
	{
		if ($fragment) return $this->language.'/'.$this->controller;
		return $this->internal_url;
	}
	
	/**
	 * returns the name of the currenr controller
	 *
	 * @var string
	 */
	public function controller()
	{
		return $this->controller;
	}
	
	/**
	 * returns a part of the url by its index number.
	 *
	 * @param int $index
	 * @return mixed
	 */
	public function slice($index)
	{
		if ( ! isset($this->slices[$index])) return null;
		return $this->slices[$index];
	}
}

/**
 * Kalaisoo Controller Class.
 *
 * This class is the master of all application controllers.
 *
 * @package Kalaisoo
 * @subpackage System
 * @author $Author: info@sah-company.com $
 */
class Controller
{
	/**
	 * @var Registry
	 */
	protected $registry;

	/**
	 * @var mixed
	 */
	protected $user;

	/**
	 * @var Template
	 */
	public $template;
	
	/**
	 * @var Permission
	 */
	protected $permission;
	
	/**
	 * This array holds view data used within a view.
	 *
	 * @var array
	 */
	public $data = array(
		'pageTitle' => APP_NAME,
		'avatar' => array(
			'size' => AVATAR_SIZE
		),
		'errors' => array(),
		'messages' => array(),
		'styles' => array(
			'core'
		),
		'scripts' => array(),
		'meta' => array(
			'author' => AUTHOR_EMAIL,
			'description' => META_DESCRIPTION,
			'keywords' => META_KEYWORDS
		)
	);
	
	/**
	 * @var Input
	 */
	public $input;

	/**
	 * constructs a new Controller and setup the Registry and load the core language file.
	 */
	public function __construct()
	{
		$this->registry = Registry::instance();
		$this->registry->loader->language('core');
	}
	
	/**
	 * returns either the user or null if no user is set or sets the user and returns it.
	 *
	 * This will either set or get the user. If no parameter is given the current
	 * user is returned. Otherwise the user is set to the user given and returned.
	 *
	 * @param mixed $user
	 * @return mixed
	 */
	public function user($user = null)
	{
		if ($user !== null) {
			$this->user = $user;
			return $this->user;
		}
		if ( ! isset($this->user)) {
			return null;
		}
		return $this->user;
	}
	
	/**
	 * returns either the Permission object or null if none is set.
	 *
	 * @return mixed Either the permission object or null
	 */
	public function permission()
	{
		if ( ! isset($this->permission)) {
			return null;
		}
		return $this->permission;
	}
	
	/**
	 * either calls the a model method or loads a plugin.
	 *
	 * This interceptor can be used to call a plugin for your controllers.
	 * To install a plugin simply store a php file in the plugins folder.
	 *
	 * @see app/plugins/scaffold
	 *
	 * @param string $method
	 * @param array $params
	 * @return mixed
	 */
	public function __call($method, array $params = array())
	{
		if ( ! $this->registry->loader->plugin($method)) {
			return $this->error(__('error_404_plugin'));
		}
		$this->registry->loader->lib(array('redbean', 'permission'));
		$this->permission = new Permission();
		$pluginName = ucfirst(strtolower($method)).'_Plugin';
		$plugin = new $pluginName();
		$plugin->setController($this);
		return call_user_func_array(array($plugin, 'execute'), $params);
	}
	
	/**
	 * redirects the client to a url and terminates program execution.
	 *
	 * the url given in the parameter should be an absolute url with preceeding
	 * protocol name, e.g. http://yourserver.com/ or https://secrect.com/welcome/here
	 *
	 * @param string $url
	 */
	public function redirect($url)
	{
		header('Location: '.BASEHREF.'/'.$url);
		exit;
	}
	
	/**
	 * redirects to the referer or the users startpage and terminates program execution.
	 */
	protected function redirectToReferer()
	{
		if ( ! $goto = $this->input->goto) $goto = $this->user->startpage();
		$this->redirect($goto);
		exit; // for safety only
	}
	
	/**
	 * outputs the error template to the client browser.
	 *
	 * @param string $errorText
	 */
	public function error($errorText = null)
	{
		$this->registry->loader->language('error');
		$this->registry->loader->helper(array('textile', 'url'));
		$template = new Template();
		$template->view('errors/error', array('page_title' => __('error_title'), 'error' => $errorText));
	}
}

/**
 * Kalaisoo Plugin Class.
 *
 * The plugin is injected into the controller when a method is called that is not
 * available to the public in the given controller. The controller then instanciates
 * this plugin and reinjects itself and the registry into the plugin.
 *
 * @package Kalaisoo
 * @subpackage System
 * @author $Author: info@sah-company.com $
 */
class Plugin
{
	/**
	 * @var Registry
	 */
	protected $registry;
	
	/**
	 * @var Controller
	 */
	protected $controller;

	/**
	 * constructs a new Plugin and sets up the Registry.
	 */
	public function __construct()
	{
		$this->registry = Registry::instance();
	}
	
	/**
	 * inject the controller into the plugin.
	 *
	 * @param Controller
	 */
	public function setController(Controller $controller)
	{
		$this->controller = $controller;
	}
	
	/**
	 * returns the controller object.
	 *
	 * @return Controller
	 */
	public function controller()
	{
		return $this->controller;
	}
}

/**
 * Kalaisoo Template Class.
 *
 * This class loads a template file, mixes it with data and outputs it to the client.
 *
 * @package Kalaisoo
 * @subpackage System
 * @author $Author: info@sah-company.com $
 */
class Template
{
	/**
	 * data that will be mixed with the template.
	 *
	 * @var array
	 */
	public $data = array();

	/**
	 * constructs a new Template and sets the language in data.
	 */
	public function __construct()
	{
		$this->data['language'] = Registry::instance()->language;
	}
	
	/**
	 * logs that there was a call to a view helper.
	 *
	 * @todo implement
	 *
	 * @param string $method
	 * @param array $params
	 */
	public function __call($method, array $params = array())
	{
		Registry::instance()->logger->log('Viewhelper called was '.$method);
	}
	
	/**
	 * extracts data into $this and include the template file if it exits.
	 *
	 * The data given in $data is mixed with the templates data. If the third parameter is
	 * true the include is buffered and returned as a string instead of being just included.
	 *
	 * @todo $template could be a bean also, not a file
	 *
	 * @param mixed $template
	 * @param array $data
	 * @param bool
	 */
	public function view($template, array $data = array(), $return = false)
	{
		if ( ! file_exists(APP.'/views/'.$template.EXT)) {
			return false;
		}
		$this->data = array_merge($this->data, $data);
		extract($this->data);
		if ($return === true)
		{
			ob_start();
			include APP.'/views/'.$template.EXT;
			$contents = ob_get_contents();
			ob_end_clean();
			return $contents;
		}
		include APP.'/views/'.$template.EXT;
	}
}

/**
 * Kalaisoo Request Class.
 *
 * This class analyses http requests and provides information about it.
 *
 * @package Kalaisoo
 * @subpackage System
 * @author $Author: info@sah-company.com $
 */
class Request
{
	/**
	 * The http protocol.
	 *
	 * @const string
	 */
	const HTTP_PROTOCOL = 'http://';
	
	/**
	 * The https protocol.
	 *
	 * @const string
	 */
	const HTTPS_PROTOCOL = 'https://';

	/**
	 * constructs a new Request.
	 */
	public function __construct()
	{
	}
	
	/**
	 * return the server name preceeded by the protocol.
	 *
	 * @return string
	 */
	public function host()
	{
		$protocol = self::HTTP_PROTOCOL;
		if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'])
		{
			$protocol = self::HTTPS_PROTOCOL;
		}
		return $protocol.$_SERVER['SERVER_NAME'];
	}
	
	/**
	 * returns the clients request type, either post or get.
	 *
	 * @return string
	 */
	public function type()
	{
		if (count($_POST) == 0) return 'get';
		return 'post';
	}

	/**
	 * returns true if the clients request was an ajax call.
	 *
	 * Checks the SERVER variable to see if this was an ajax initiated request.
	 * Your controller can then decide wether to output a complete HTML page or
	 * only a certain partial view.
	 *
	 * @return bool
	 */
	public function isAjax() {
	    return (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest');
	}
}

/**
 * Kalaisoo Input Class.
 *
 * The Input class gives to access to user input given via a post or get http request.
 * You may instanciate an input object in your controller or in your plugin.
 * It is not in our registry, because some action simply do not need it.
 * It uses a magic getter to lookup a parameter from user input.
 * It automatically checks for get or post requests and sanatizes
 * the input value returned.
 *
 * Example:
 * <code>
 * <?php
 *  $input = new Input();
 *  $firstname = $input->firstname;
 * ?>
 * </code>
 *
 * @package Kalaisoo
 * @subpackage System
 * @author $Author: info@sah-company.com $
 */
class Input
{
	/**
	 * Registry instance.
	 *
	 * @var Registry
	 */
	protected $registry;
	
	/**
	 * cached, already requested values.
	 *
	 * @var array
	 */
	protected $values = array();
	
	/**
	 * flag to see if the input was already analyzed.
	 * 
	 * @var bool
	 */
	protected $alreadyAnalyzed = false;

	/**
	 * the get parameters.
	 *
	 * @var array
	 */
	protected $getParameters = array();

	/**
	 * constructs a new Input and sets the registry.
	 */
	public function __construct()
	{
		$this->registry = Registry::instance();
	}
	
	/**
	 * returns wether a property of $name exists or not.
	 *
	 * @param string $name
	 * @return bool
	 */
	public function __isset($name)
	{
		return (bool)$this->$name;
	}
	
	/**
	 * returns the value of a property.
	 *
	 * This method return either the POST or GET value of a named input request
	 * parameter unless it was already requested before.
	 * Than it will will return the beforehand
	 * sanatized and filtered value for the named parameter.
	 *
	 * @param string $name
	 * @return mixed
	 */
	public function __get($name)
	{
		if (isset($this->values[$name])) return $this->values[$name];
		if ($this->registry->request->type() == 'post') return $this->getPost($name);
		return $this->getGet($name);
	}
	
	/**
	 * returns the value of property $name from POST client data.
	 *
	 * @param string $name
	 * @return mixed
	 */
	protected function getPost($name)
	{
		if ( ! isset($_POST[$name])) return null;
		return $this->safe($_POST[$name], $name);
	}
	
	/**
	 * returns the value of property $name from GET client data.
	 *
	 * @param string $name
	 * @return mixed
	 */
	protected function getGet($name)
	{
		if ( ! isset($_GET[$name])) return null;
		return $this->safe($_GET[$name], $name);
	}
	
	/**
	 * returns the filtered, sanitized and (safe) value of a property.
	 *
	 * @param mixed $value
	 * @param string $name
	 * @return mixed
	 */
	protected function safe($value, $name)
	{
		if ( is_array($value)) return $this->safeArray($value, $name);
		return $this->values[$name] = filter_var($value, FILTER_SANITIZE_STRING, FILTER_FLAG_NO_ENCODE_QUOTES);
	}
	
	/**
	 * returns a filterd, sanitized array of a property.
	 *
	 * @todo Do this with filter_var_input?
	 * @todo make this safe for multi-arrays
	 *
	 * @param array $array
	 * @param string $name
	 * @return array
	 */
	protected function safeArray($array, $name)
	{
		return $this->values[$name] = $array;
		// the real thing is more like this
		$ret = array();
		foreach ($array as $key=>$value)
		{
			$ret[$key] = filter_var($value, FILTER_SANITIZE_STRING, FILTER_FLAG_NO_ENCODE_QUOTES);
		}
		return $this->values[$name] = $ret;
	}
}

/**
 * Kalaisoo Response Class.
 *
 * This class handles the output of the application.
 *
 * @package Kalaisoo
 * @subpackage System
 * @author $Author: info@sah-company.com $
 */
class Response
{
	/**
	 * stores the headers of our response.
	 *
	 * @var array
	 */
	protected $headers = array();
	
	/**
	 * stores the replacement values which are replaced when the response is output.
	 *
	 * @var array
	 */
	protected $replacements = array();

	/**
	 * constructs a new response.
	 */
	public function __construct()
	{
	}
	
	/**
	 * starts the outbut buffer.
	 */
	public function start()
	{
		ob_start();
	}
	
	/**
	 * clears the active output buffer.
	 */
	public function clean()
	{
		ob_clean();
	}
	
	/**
	 * echos the response to the client.
	 *
	 * Gets the contents of the output buffer, replaces all replacements tokens and
	 * saves a cached file if our cache is active.
	 * Then the headers are send while afterwards the buffer is echoed to the client.
	 */
	public function flush()
	{
		$buffer = $this->replacements(ob_get_contents());
		ob_end_clean();
		if (Registry::instance()->cache->isActive())
		{
			Registry::instance()->cache->file($buffer);
		}
		$this->headers();
		echo $buffer;
	}
	
	/**
	 * replaces all tokens like {yourtoken} with its replacement values and returns the text.
	 *
	 * This will try to replace all replacements tokens in the given string and return
	 * the string where all found tokens were replaced by their equivalents.
	 *
	 * @param string $text
	 * @return string
	 */
	protected function replacements($text)
	{
		foreach ($this->replacements as $key=>$value) {
			$needle = '{'.$key.'}';
			$text = str_replace($needle, $value, $text);
		}
		return $text;
	}
	
	/**
	 * add a new replacement token.
	 *
	 * @param string $key
	 * @param mixed $value
	 */
	public function addReplacement($key, $value)
	{
		$this->replacements[$key] = $value;
	}
	
	/**
	 * add a header.
	 *
	 * @param string $key
	 * @param mixed $value
	 */
	public function addHeader($key, $value)
	{
		$this->headers[$key] = $value;
	}
	
	/**
	 * send headers to the client.
	 */
	public function headers()
	{
		foreach ($this->headers as $header=>$value)
		{
			header('{$header}: {$value}');
		}
	}
}

/**
 * Kalaisoo Cache Class.
 *
 * This class lets you save a string as a file in the cache folder.
 *
 * @package Kalaisoo
 * @subpackage System
 * @author $Author: info@sah-company.com $
 */
class Cache
{
	/**
 	 * Lifetime of the cached files in seconds. Set to 0 to turn off caching.
	 *
	 * @var int
	 */
	protected $lifetime = 0;

	/**
	 * hashed url as cache filename.
	 *
	 * @var string
	 */
	protected $hash;
	
	/**
	 * holds the path/to/cachefile.
	 *
	 * @var string
	 */
	protected $file;
	
	/**
	 * constructs a new Cache and sets lifetime to what the config file says.
	 */
	public function __construct()
	{
		$this->lifetime = Registry::instance()->config['cache']['lifetime'];
	}
	
	/**
	 * sets the lifetime of the cached file in seconds or turns caching off is set to 0.
	 *
	 * @param int $seconds
	 */
	public function setLifetime($seconds = 0)
	{
		$this->lifetime = $seconds;
	}
	
	/**
	 * returns wether the cache is active or not.
	 *
	 * @return bool
	 */
	public function isActive()
	{
		return (bool)$this->lifetime;
	}
	
	/**
	 * outputs a cached file and exits or returns doing nothing.
	 *
	 * This method builds the hash from the requests URI. If a file named after
	 * that hash exists in the cache folder and it is not older than our cache
	 * file lifetime limit it is send to the browser and
	 * our application exits. Otherwise nothing happens and the
	 * application continues normally.
	 *
	 * @return void
	 */
	public function check()
	{
		$this->hash();
		if ( ! file_exists($this->file)) return;
		clearstatcache();
		if (filemtime($this->file) <= (time() - $this->lifetime)) return;
		Registry::instance()->response->headers();
		include $this->file;
		exit;
	}
	
	/**
	 * writes the cached file to filesystem.
	 *
	 * @param string $content
	 */
	public function file($content)
	{
		$this->hash();
		Filesystem::write($this->file, $content);
	}
	
	/**
	 * sets has and filename for this Cache.
	 */
	protected function hash()
	{
		$this->hash = md5($_SERVER['REQUEST_URI']);
		$this->file = BASEPATH.'/cache/cache_'.$this->hash.'.html';
	}
}

/**
 * Kalaisoo Stopwatch Class.
 *
 * This class lets you mark stoptimes, measured in microtime. With the laptime() method
 * you can retrieve the elapsed time between two marks.
 *
 * @package Kalaisoo
 * @subpackage System
 * @author $Author: info@sah-company.com $
 */
class Stopwatch
{	
	/**
	 * holds the timestamps.
	 *
	 * @var array
	 */
	protected $marks = array();
	
	/**
	 * constructs a new Stopwatch and stamps a mark _start for internal use.
	 */
	public function __construct()
	{
		$this->mark('_start');
	}
	
	/**
	 * adds a mark and returns $this.
	 *
	 * A mark is set with the current microtime.
	 *
	 * @param string $mark
	 * @return $this
	 */
	public function mark($mark)
	{
		$this->marks[$mark] = microtime(true);
		return $this;
	}
	
	/**
	 * returns the distance between two marks in miliseconds or null with n digits precision.
	 *
	 * @param string $mark1
	 * @param string $mark2
	 * @param int $decimal
	 * @return mixed
	 */
	public function laptime($mark1, $mark2, $decimal = 5)
	{
		if ( ! isset($this->marks[$mark1]) or ! isset($this->marks[$mark2]))
		{
			return null;
		}
		return round($this->marks[$mark2] - $this->marks[$mark1], $decimal);
	}
	
	/**
	 * resets all marks, but _start mark and returns $this.
	 *
	 * @return Stopwatch
	 */
	public function reset()
	{
		$start = $this->marks['_start'];
		$this->marks = array();
		$this->marks['_start'] = $start;
		return $this;
	}
}

/**
 * Kalaisoo Filesystem Class.
 *
 * @package Kalaisoo
 * @subpackage System
 * @author $Author: info@sah-company.com $
 */
class Filesystem
{
	/**
	 * @var string
	 */
	public static $error;

	/**
	 * deletes a file from the filesystem and returns the operations result.
	 *
	 * @param string $file
	 * @return bool
	 */
	public static function unlink($file)
	{
		if ( ! is_file($file)) return false;
		if ( ! is_writeable($file)) return false;
		return unlink($file);
	}

	/**
	 * appends a file with content and returns the result of the operation.
	 *
	 * @param string $file
	 * @param string $content
	 * @return bool
	 */
	public static function append($file, $content)
	{
		return self::write($file, $content, 'a');
	}
	
	/**
	 * writes a content to file and either return true or exit program with error message.
	 *
	 * @todo on error exceptions should be thrown but not exit()
	 *
	 * @param string $path
	 * @param string $content
	 * @param string $modus
	 * @return bool
	 */
	public static function write($file, $content, $modus = 'w')
	{
		if (empty($content)) return;
		$handle = fopen($file, $modus);
		if ( ! $handle) exit('Could not acquire a file handle');
		if ( ! flock($handle, LOCK_EX)) exit('Could not lock the file exclusive');
		if ( ! fwrite($handle, $content)) exit('Failed to write to file');
		if ( ! flock($handle, LOCK_UN)) exit('Could not unlock the file');
		if ( ! fclose($handle)) exit('Failed to close file');
		return true;
	}
	
	/**
	 * returns a files content.
	 *
	 * @param string $file
	 * @return string
	 */
	public static function contents($file)
	{
		return file_get_contents($file);
	}
	
	/**
	 * moves a uploaded file to the upload directory specified in configuration or not.
	 *
	 * The nice regex to get a clean filename i found here:
	 * @see http://iamcam.wordpress.com/2007/03/20/clean-file-names-using-php-preg_replace/
	 *
	 * @param string $inputName
	 * @param string (optional) $targetFile
	 * @return mixed
	 */
	public static function moveUploadedFile($inputName, $targetFile = null)
	{
		if ( ! isset($_FILES) || ! isset($_FILES[$inputName])) return null;
		if ($_FILES[$inputName]['error'] != 0) {
			self::$error = $_FILES[$inputName]['error'];
			return false;
		}
		$dir = Registry::instance()->config['uploads']['folder'];
		if ( ! $targetFile) {
			$raw_filename = basename($_FILES[$inputName]['name']);
		} else {
			$raw_filename = $targetFile;
		}
		$filename = preg_replace('/[^\w\.]/', '', $raw_filename);
		if ( ! move_uploaded_file($_FILES[$inputName]['tmp_name'], $dir.'/'.$filename)) {
			return false;
		}
		return $filename;
	}
}

/**
 * Kalaisoo Loader Class.
 *
 * This class loads language files and other resources.
 *
 * @package Kalaisoo
 * @subpackage System
 * @author $Author: info@sah-company.com $
 */
class Loader
{
	/**
	 * already loaded tokes.
	 *
	 * @var array
	 */
	protected $memory = array();
	
	/**
	 * list of files.
	 *
	 * @var array
	 */
	protected $filelist = array();
	
	/**
	 * loads one or many language files and returns result of the operation.
	 *
	 * @param mixed $languages
	 * @param string $iso2
	 * @return bool
	 */
	public function language($languages, $iso2 = null)
	{
		if ($iso2 === null) $iso2 = Registry::instance()->language;
		if ( ! is_array($languages)) $languages = array($languages);
		$this->filelist = $languages;
		return $this->load(BASEPATH.'/languages', $iso2, false); // third param MUST be false
	}
	
	/**
	 * loads one or many helper files and returns result of the operation.
	 *
	 * @param mixed $helpers
	 * @return bool
	 */
	public function helper($helpers)
	{
		if ( ! is_array($helpers)) $helpers = array($helpers);
		$this->filelist = $helpers;
		return $this->load(SYS, 'helpers');
	}
	
	/**
	 * loads one or many model files and returns result of the operation.
	 *
	 * if the second parameter is true the languages files named like the models to load
	 * are also loaded.
	 *
	 * @param mixed $models
	 * @param bool $load_language
	 * @return bool
	 */
	public function model($models, $load_language = false)
	{
		if ( ! is_array($models)) $models = array($models);
		$this->filelist = $models;
		return $this->load(APP, 'models', $load_language);
	}
	
	/**
	 * loads one or many library files and returns result of the operation.
	 *
	 * @param mixed $libraries
	 * @return bool
	 */
	public function lib($libraries)
	{
		if ( ! is_array($libraries)) $libraries = array($libraries);
		$this->filelist = $libraries;
		return $this->load(SYS, 'lib');
	}
	
	/**
	 * loads one or many plugin files and returns result of the operation.
	 *
	 * @param mixed $plugins
	 * @return bool
	 */
	public function plugin($plugins)
	{
		if ( ! is_array($plugins)) $plugins = array($plugins);
		$this->filelist = $plugins;
		return $this->load(APP, 'plugins');
	}
	
	/**
	 * loads all files in filelist from a path and return result of the operation.
	 *
	 * This does really try to load the file if it exists and stuffs it into memory.
	 * If the optional parameter $load_language is true, this will try to load the
	 * language file as well.
	 *
	 * @param string $path
	 * @param string $token
	 * @param bool $load_language
	 * @return bool
	 */
	protected function load($path, $token, $load_language = false)
	{
		$afterloader = array();
		foreach ($this->filelist as $file) {
			//if (isset($this->memory[$token][$file])) continue; // already loaded... cont.
			if ( ! file_exists($path.'/'.$token.'/'.$file.EXT)) {
				Registry::instance()->logger->log(sprintf('Missing "%s" in "%s"', $file, $token));
				return false;
			}
			if ($load_language) {
				$afterloader[] = $file;
			}
			if (isset($this->memory[$token][$file])) continue;
			$this->memory[$token][$file] = true;
			include $path.'/'.$token.'/'.$file.EXT;
		}
		if ( ! empty($afterloader)) {
			// load the language files also
			$this->language($afterloader);
		}
		return true;
	}
}

/**
 * Kalaisoo Logger Class.
 *
 * This class logs messages and may store them in files depending on the settings
 * you have made in app/config/config.php.
 *
 * @package Kalaisoo
 * @subpackage System
 * @author $Author: info@sah-company.com $
 */
class Logger
{
	/**
	 * log entries.
	 *
	 * @var array
	 */
	protected $log = array();
	
	/**
	 * adds a new entry in the log specified by $file.
	 *
	 * @param string $message
	 * @param string $file
	 */
	public function log($message, $file = 'flow')
	{
		$this->log[$file][] = date('Y-m-d H:i:s').' '.$_SERVER['REMOTE_ADDR'].': '.$message;
	}
	
	/**
	 * appends the log entries to the logs files and returns the result of the operation.
	 *
	 * @return bools
	 */
	public function save()
	{
		foreach ($this->log as $file=>$messages)
		{
			if (empty($messages)) continue;
			$content = implode("\n", $messages)."\n";
			$ret = Filesystem::append(BASEPATH.'/logs/'.$file.'.log', $content);
		}
		return $ret;
	}
}

/**
 * Kalaisoo Database Class.
 *
 * This class is the lets you connect to a database. It is used by our Session class to
 * connect to the database and use that PDO connection to lookup, save,
 * delete or otherwise manipulate the records in your database.
 * You could always use this to communicate with your database,
 * although as for now we have decided to use RedBean 2 as ORM.
 *
 * @package Kalaisoo
 * @subpackage System
 * @author $Author: info@sah-company.com $
 */
class Database
{
	/**
	 * instance of the one and only Database object.
	 *
	 * @var Database
	 */
	private static $instance = null;
	
	/**
	 * configuration array.
	 * 
	 * @var array
	 */
	protected $config = array();
	
	/**
	 * database connection resource, database handler.
	 *
	 * @var mixed
	 */
	public $dbh;
	
	/**
	 * returns the one and only Database object.
	 *
	 * @return Database
	 */
	public static function instance()
	{
		if (self::$instance === null)
		{
			self::$instance = new Database();
		}
		return self::$instance;
	}
	
	/**
	 * connects to the database and sets the database handler dbh.
	 *
	 * @uses PDO
	 */
	public function connect()
	{
		$this->dbh = new PDO($this->config['driver'].
							':host='.$this->config['host'].
							';dbname='.
							$this->config['database'],
							$this->config['username'],
							$this->config['password'],
							$this->config['options']
						);
	}
	
	/**
	 * constructs a new Database and sets configurations.
	 */
	protected function __construct()
	{
		$this->config = Registry::instance()->config['db'];
	}
	
	/**
	 * declared private so it can not be cloned.
	 */
	private function __clone()
	{
	}
}

/**
 * Kalaisoo Session Class.
 *
 * This class is our session handler.
 *
 * @package Kalaisoo
 * @subpackage System
 * @author $Author: info@sah-company.com $
 */
class Session
{
	/**
	 * name of the session cookie.
	 *
	 * @const string
	 */
	const COOKIE_NAME = 'refsessv1';

	/**
	 * name of the session table.
	 * 
	 * @const string
	 */
	const TABLE_SESSION = 'session';
	
	/**
	 * the table name.
	 *
	 * @var string
	 */
	public static $table_name;
	
	/**
	 * the PDO database handler.
	 *
	 * @var mixed
	 */
	public static $db;
		
	/**
	 * opens a new session and returns true.
	 *
	 * @param string $save_path
	 * @param string $session_id
	 * @return bool
	 */
	public static function open($save_path, $session_id)
	{
		self::$db = Database::instance();
		self::$table_name = self::TABLE_SESSION;
		return true;
	}

	/**
	 * closes the session.
	 */
    public static function close()
	{
	}
    
	/**
	 * returns the session data or an empty string.
	 *
	 * @param string $session_id
	 * @return mixed
	 */
	public static function read($session_id)
	{
		if ( ! self::$db->dbh)
		{
			self::$db->connect();
		}
		$stmt = self::$db->dbh->prepare("SELECT data FROM ".self::$table_name." WHERE id=:id");
		$ret = $stmt->execute(array(':id' => $session_id));
		if ( ! $ret or ! $session_data = $stmt->fetchColumn())
		{
			return ''; // essential to return empty string
		}
		return $session_data;
	}
	
	/**
	 * writes the session to the database.
	 *
	 * @param string $session_id
	 * @param string $session_data
	 * @return bool
	 */
    public static function write($session_id, $session_data)
	{
		Registry::instance()->logger->log(sprintf('Session write id "%s" with data "%s"', $session_id, $session_data));
		if ( ! self::$db->dbh)
		{
			self::$db->connect();
		}
		$stmt = self::$db->dbh->prepare("REPLACE INTO ".self::$table_name." (id, last_update, data) VALUES (:id, :last_update, :data)");
		return $stmt->execute(
			array(
				':id' => $session_id,
				':last_update' => time(),
				':data' => $session_data
			)
		);
	}

	/**
	 * deletes the session record from the database.
	 *
	 * @param string $session_id
	 * @return bool
	 */
    public static function destroy($session_id)
	{
		$_SESSION = array();
		if (ini_get("session.use_cookies"))
		{
		    $params = session_get_cookie_params();
		    setcookie(session_name(), '', time() - 42000,
        		$params["path"], $params["domain"],
		        $params["secure"], $params["httponly"]
    		);
		}
		if ( ! self::$db->dbh)
		{
			self::$db->connect();
		}
		$stmt = self::$db->dbh->prepare("DELETE FROM ".self::$table_name." WHERE id=:id");
		return $stmt->execute(array(':id' => $session_id));
	}

	/**
	 * deletes all old and outdated sessions from the database.
	 *
	 * @param int $max_lifetime
	 * @return bool
	 */
    public static function gc($max_lifetime)
	{
		if ( ! self::$db->dbh)
		{
			self::$db->connect();
		}
		$stmt = self::$db->dbh->prepare("DELETE FROM ".self::$table_name." WHERE last_update<:outdated");
		return $stmt->execute(array(':outdated' => time()-$max_lifetime));
	}
}

/**
 * Kalaisoo Mock Class.
 *
 * The Mock class can be used to simulate a class not yet implemented. It is used
 * for testing purposes and for defining a class methods signatures and their
 * return values.
 *
 * @package Kalaisoo
 * @subpackage System
 * @author $Author: info@sah-company.com $
 */
class Mock
{
	/**
	 * name of the class to mock.
	 *
	 * @var string
	 */
	protected $class_name = '_unknown_class';
	
	/**
	 * properties of the mocked class.
	 *
	 * @var array
	 */
	protected $vars = array();
	
	/**
	 * expected values for return values.
	 *
	 * @var array
	 */
	protected $expected_values = array();
	
	/**
	 * name of the latest called method.
	 *
	 * @var string
	 */
	protected $last_method;
	
	/**
	 * constructs a new Mock and sets the desired class name.
	 *
	 * @param string $class_name
	 */
	public function __construct($class_name)
	{
		$this->class_name = $class_name;
	}
	
	/**
	 * runs the method if it really exists or pretends to.
	 *
	 * @param string $name
	 * @param array $args
	 * @return $this
	 */
	public function __call($name, $args)
	{
		if (method_exists(__CLASS__, $name))
		{
			return $this->$name($args);
		}
		$this->last_method = $name;
		return $this;
	}
	
	/**
	 * returns the name of the class.
	 *
	 * @return string
	 */
	public function __toString()
	{
		return $this->class_name;
	}
	
	/**
	 * sets the value of a property.
	 *
	 * @param string $name
	 * @param mixed $value
	 */
	public function __set($name, $value)
	{
		$this->vars[$name] = $value;
	}
	
	/**
	 * returns the value of a property or null.
	 *
	 * @param string $name
	 * @return mixed
	 */
	public function __get($name)
	{
		if (isset($this->vars[$name]))
		{
			return $this->vars[$name];
		}
		return null;
	}
	
	/**
	 * returns wether the property is set or not.
	 *
	 * @param string $name
	 * @return bool
	 */
	public function __isset($name)
	{
		if (isset($this->vars[$name]))
		{
			return true;
		}
		return false;
	}
	
	/**
	 * unset a property.
	 *
	 * @param string $name
	 */
	public function __unset($name)
	{
		if ( ! isset($this->vars[$name])) return;
		unset($this->vars[$name]);
	}

	/**
	 * returns the mocked result of a function call on the mocked class.
	 *
	 * @param array $value
	 * @return mixed $value
	 */
	public function and_return($value)
	{
		return $value;
	}
}

/**
 * Rewriter tags all off.
 */
ini_set('url_rewriter.tags', '');

/**
 * set session.* so that only cookies are used.
 */
ini_set('session.use_trans_sid', '0');
ini_set('session.use_cookies', '1');
ini_set('session.use_only_cookies', '1');

/**
 * set session handler to "user".
 */
ini_set('session.save_handler', 'user');

/**
 * set the session handler to class "Session".
 *
 * @see Session
 */
session_set_save_handler(
	array('Session', 'open'),
	array('Session', 'close'),
	array('Session', 'read'),
	array('Session', 'write'),
	array('Session', 'destroy'),
	array('Session', 'gc')
);
?>